package org.kisekiproject.instance {
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;
	
	import org.kisekiproject.evaluator.IExpression;
	import org.kisekiproject.evaluator.IVariableNamespace;
	import org.kisekiproject.events.KisekiEvent;
	import org.kisekiproject.resources.ResourceLoader;

	/**
	 * @author mikael
	 */
	public class Instance extends EventDispatcher {

		private var _definition:InstanceDefinition;
		private var _object:Object;
		private var _propertyByExpression:Dictionary;
		private var _expressionByProperty:Dictionary;
		private var _source:XML;
		private var _factory:InstanceFactory;
		private var _id:String;

		/**
		 * Constructor.
		 */
		public function Instance(source:XML, instanceFactory:InstanceFactory) {
			_definition=instanceFactory.definitions.getDefinition(source.name());

			if (!_definition)
				throw new Error("Unknown class: "+source.name());

			_source=source;
			_id=String(_source.@id);
			_factory=instanceFactory;
			_propertyByExpression=new Dictionary();
			_expressionByProperty=new Dictionary();

			for each (var x:XML in _source.children())
				parseProperty(x);

			for each (var a:XML in _source.attributes())
				if (a.name()!="id")
					parseProperty(a);
		}

		/**
		 * Get instanciated object.
		 */
		public function get object() : Object {
			if (!_object)
				instantiate();

			return _object;
		}

		/**
		 * Get definition.
		 */
		public function get definition() : InstanceDefinition {
			return _definition;
		}

		/**
		 * Instantiate.
		 */
		private function instantiate():void {
			var cls:Class=_definition.instanceClass;
			_object=new cls();

			for (var p:String in _expressionByProperty) {
				var ex:PropertyExpression=_expressionByProperty[p];

				_object[p]=ex.value;
			}

			if (_object is EventDispatcher) {
				EventDispatcher(_object).addEventListener(KisekiEvent.OUTPUT_CHANGE,onOutputChange);
				EventDispatcher(_object).dispatchEvent(new KisekiEvent(KisekiEvent.CREATION_COMPLETE));
			}

			if (_object["initialize"] is Function)
				_object["initialize"]();

			dispatchEvent(new Event(Event.COMPLETE));
		}

		/**
		 * Output change.
		 */
		protected function onOutputChange(event:Event):void {
			dispatchEvent(new KisekiEvent(KisekiEvent.OUTPUT_CHANGE));
		}

		/**
		 * Set property expression.
		 */
		private function parseProperty(source:XML):void {
			var property:String=source.name();
			var def:PropertyDefinition=_definition.getPropertyDefinitionByName(property);

			if (!def)
				throw new Error("Unknown property: "+property);

			if (_expressionByProperty[property])
				throw new Error("Multiple property def: "+property);

			var expression:PropertyExpression=new PropertyExpression(def,source,_factory);
			expression.addEventListener(Event.CHANGE, onPropertyExpressionChange);

			_propertyByExpression[expression]=property;
			_expressionByProperty[property]=expression;
		}

		/**
		 * Expression change.
		 */
		private function onPropertyExpressionChange(event : Event) : void {
			if (!_object)
				return;

			var expression:IExpression=IExpression(event.target);
			var property:String=_propertyByExpression[expression];

			_object[property]=expression.value;
		}

		/**
		 * Set id.
		 */
		public function set id(value:String):void {
			if (_id)
				throw new Error("Id already set");

			_id=value;
		}

		/**
		 * Get id.
		 */
		public function get id():String {
			return _id;
		}

		/**
		 * Get required resources.
		 */
		public function get requiredResources():Array {
			var a:Array=new Array();

			for (var p:String in _expressionByProperty) {
				var ex:PropertyExpression=_expressionByProperty[p];

				a=a.concat(ex.requiredResources);
			}

			return a;
		}
	}
}
